/**
 * @file   AbstractPacket.cpp
 * @author Djuro Drljaca (djurodrljaca@gmail.com)
 * @date   2014-04-21
 * @brief  Base object for a DON packet.
 *
 * Copyright 2014  Djuro Drljaca (djurodrljaca@gmail.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <DON/Core/Packet/AbstractPacket.h>
#include <DON/Core/Packet/PacketHeader.h>
#include <DON/Core/Packet/PacketDefines.h>
#include <DON/Core/Crc16/Crc16.h>
#include <DON/Core/DataType/UInt16.h>
#include <QtCore/QByteArray>

using namespace DON::Core::Packet;

class AbstractPacket::Private
{
public:
    Private()
        : source(0u),
          destination(0u),
          packetType(PacketType_Invalid),
          sequence(0u)
    { }

    QByteArray createHeaderData(const QByteArray &payloadData, bool *ok) const;

    quint16 source;
    quint16 destination;
    PacketType packetType;
    quint16 sequence;
};

AbstractPacket::AbstractPacket()
    : d(new Private())
{
}

AbstractPacket::AbstractPacket(const quint16 source,
                               const quint16 destination,
                               const PacketType packetType,
                               const quint16 sequence)
    : d(new Private())
{
    d->source = source;
    d->destination = destination;
    d->packetType = packetType;
    d->sequence = sequence;
}

AbstractPacket::AbstractPacket(const AbstractPacket &other)
    : d(new Private())
{
    d->source = other.d->source;
    d->destination = other.d->destination;
    d->packetType = other.d->packetType;
    d->sequence = other.d->sequence;
}

AbstractPacket::~AbstractPacket()
{
    delete d;
}

AbstractPacket & AbstractPacket::operator=(const AbstractPacket &other)
{
    d->source = other.d->source;
    d->destination = other.d->destination;
    d->packetType = other.d->packetType;
    d->sequence = other.d->sequence;

    return *this;
}

bool AbstractPacket::isGenericPacketInstance() const
{
    return false;
}

quint16 AbstractPacket::getSource() const
{
    return d->source;
}

void AbstractPacket::setSource(const quint16 source)
{
    d->source = source;
}

quint16 AbstractPacket::getDestination() const
{
    return d->destination;
}

void AbstractPacket::setDestination(const quint16 destination)
{
    d->destination = destination;
}

PacketType AbstractPacket::getPacketType() const
{
    return d->packetType;
}

bool AbstractPacket::setPacketType(const PacketType packetType)
{
    bool success = false;

    if (isPacketTypeValid(packetType))
    {
        d->packetType = packetType;
        success = true;
    }

    return success;
}

quint16 AbstractPacket::getSequence() const
{
    return d->sequence;
}

void AbstractPacket::setSequence(const quint16 sequence)
{
    d->sequence = sequence;
}

int AbstractPacket::getMaxPayloadDataItemCount()
{
    return DON_CORE_PACKET_MAX_PAYLOAD_DATA_ITEM_COUNT;
}

QByteArray AbstractPacket::toBinary() const
{
    QByteArray packetData;

    if (isValid())
    {
        bool success = false;

        // Prepare payload data
        const QList<QByteArray> payloadDataItems = getPayloadDataItems(&success);
        QByteArray payloadData;

        if (success)
        {
            // Add Item Count
            const int size = payloadDataItems.size();

            if ((size >= 0) &&
                (size <= getMaxPayloadDataItemCount()))
            {
                payloadData.append(static_cast<char>(size));
            }
            else
            {
                // Error
                payloadData.clear();
                success = false;
            }

            // Add Item List
            if (success)
            {
                for (int index = 0; index < size; ++index)
                {
                    // Add Item
                    const QByteArray itemData = payloadDataItems.at(index);

                    if (itemData.isEmpty() == false)
                    {
                        payloadData.append(itemData);
                    }
                    else
                    {
                        // Error
                        payloadData.clear();
                        success = false;
                        break;
                    }
                }
            }
        }

        // Prepare header data
        QByteArray headerData;

        if (success)
        {
            headerData = d->createHeaderData(payloadData, &success);
        }

        // Create packet data
        if (success)
        {
            packetData.append(static_cast<char>(DON_CORE_PACKET_STX_VALUE));
            packetData.append(headerData);
            packetData.append(payloadData);
            packetData.append(static_cast<char>(DON_CORE_PACKET_ETX_VALUE));
        }
    }

    return packetData;
}

QByteArray AbstractPacket::toBinary(const QByteArray &payloadData) const
{
    // Prepare header data
    bool success = false;
    const QByteArray headerData = d->createHeaderData(payloadData, &success);

    // Create packet data
    QByteArray packetData;

    if (success)
    {
        packetData.append(static_cast<char>(DON_CORE_PACKET_STX_VALUE));
        packetData.append(headerData);
        packetData.append(payloadData);
        packetData.append(static_cast<char>(DON_CORE_PACKET_ETX_VALUE));
    }

    return packetData;
}

QByteArray AbstractPacket::Private::createHeaderData(const QByteArray &payloadData, bool *ok) const
{
    bool success = false;
    QByteArray headerData;

    const int payloadLength = payloadData.size();

    if (payloadLength <= DON_CORE_PACKET_MAX_PAYLOAD_DATA_SIZE)
    {
        const quint16 payloadChecksum = Crc16::calculate(payloadData, 0, payloadLength, &success);

        if (success)
        {
            PacketHeader packetHeader(source,
                                      destination,
                                      packetType,
                                      sequence,
                                      static_cast<quint16>(payloadLength),
                                      payloadChecksum);

            headerData = packetHeader.toBinary();

            if (headerData.isEmpty())
            {
                success = false;
            }
        }
    }

    if (ok != NULL)
    {
        *ok = success;
    }

    return headerData;
}
